package com.hmall.cart.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.api.client.ItemClient;
import com.hmall.api.domain.dto.ItemDTO;
import com.hmall.cart.config.CartProperties;
import com.hmall.cart.domain.dto.CartFormDTO;
import com.hmall.cart.domain.po.Cart;
import com.hmall.cart.domain.vo.CartVO;
import com.hmall.cart.mapper.CartMapper;
import com.hmall.cart.service.ICartService;
import com.hmall.common.exception.BizIllegalException;
import com.hmall.common.utils.BeanUtils;
import com.hmall.common.utils.CollUtils;
import com.hmall.common.utils.UserContext;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <p>
 * 订单详情表 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2023-05-05
 */
@Service
@RequiredArgsConstructor
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {

    /*final 字段必须显式初始化：
    Java 要求所有 final 修饰的字段必须在对象构造完成前被赋值。
    这意味着你只能通过以下两种方式之一为 final 字段赋值：
    在声明时直接赋值
    在构造函数中赋值

    static不能使用构造函数
    直接赋值
    静态方法，静态代码块赋值，
    通过spring的某些途径赋值，可以用上面的
    */
    private final RestTemplate restTemplate;

    /*服务发现
    用于查询注册中心（如 Nacos、Eureka）中的微服务实例信息。
    可获取其他服务的 host、port、元数据等。
    动态服务调用
    结合 RestTemplate 或 OpenFeign，可实现动态路由（无需硬编码服务地址）。
    示例场景：替换 handleCartItems 方法中的硬编码 http://localhost:8081，改用服务名（如 item-service）从注册中心获取真实地址。
    运行时服务治理
    监控服务实例的健康状态。
    获取服务列表进行负载均衡逻辑。*/
    private final DiscoveryClient discoveryClient;
    private final ItemClient itemClient;
    private final CartProperties cartProperties;

    @Override
    public void addItem2Cart(CartFormDTO cartFormDTO) {
        // 1.获取登录用户
        Long userId = UserContext.getUser();

        // 2.判断是否已经存在
        if(checkItemExists(cartFormDTO.getItemId(), userId)){
            // 2.1.存在，则更新数量
            baseMapper.updateNum(cartFormDTO.getItemId(), userId);
            return;
        }
        // 2.2.不存在，判断是否超过购物车数量
        checkCartsFull(userId);

        // 3.新增购物车条目
        // 3.1.转换PO
        Cart cart = BeanUtils.copyBean(cartFormDTO, Cart.class);
        // 3.2.保存当前用户
        cart.setUserId(userId);
        // 3.3.保存到数据库
        save(cart);
    }

    @Override
    public List<CartVO> queryMyCarts() {
        // 1.查询我的购物车列表
        List<Cart> carts = lambdaQuery().eq(Cart::getUserId, UserContext.getUser()).list();
        if (CollUtils.isEmpty(carts)) {
            return CollUtils.emptyList();
        }

        // 2.转换VO
        List<CartVO> vos = BeanUtils.copyList(carts, CartVO.class);

        // 3.处理VO中的商品信息
        handleCartItems(vos);

        // 4.返回
        return vos;
    }

    private void handleCartItems(List<CartVO> vos) {

        //Todo
        Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
        // 这里使用resttemplate发送请求，过于复杂，使用nacos解除硬性指定地址。
        /*1.url：这个参数是请求的URL地址，可以是字符串，也可以包含URI变量。例如，网页6中的示例代码展示了如何构造带有路径变量的URL，并通过uriVariables参数传递值。
          不用加占位符（理解为这是java的），只有大括号。
        2.method：指定HTTP方法，如GET、POST等。这在网页6的示例中也有体现，使用HttpMethod.POST作为参数值。
        3.requestEntity：封装了请求头和请求体的HttpEntity对象。网页6的代码示例中创建了HttpEntity实例，
        包含请求头和参数体。网页8也提到使用HttpEntity来传递复杂的请求体。一般为null
        4。responseType：定义响应体的类型。
        .class 是 Java 中的类字面量，用于获取类的 Class 对象，它是 Java 反射机制的核心入口。
        通过 String.class、Integer.class 等，可以在运行时获取类的元数据（如方法、字段、构造器等），并动态操作类的行为。
        .class 与泛型的冲突
        在泛型代码中，直接使用 T.class 会编译失败。原因在于泛型的类型擦除机制：
        泛型类型参数（如 T）在编译后会被擦除为 Object 或其边界类型（如 T extends Number 擦除为 Number）
        这里需要注意的是，如果是复杂类型或泛型，需要使用ParameterizedTypeReference。网页7和网页8详细说明了如何通过ParameterizedTypeReference来指定嵌套的泛型结构，例如ResponseData<List<xx.Bk>>。
        5.uriVariables：用于替换URL中的路径变量或查询参数。网页6中的示例使用了Map来传递uriVariables，而网页3展示了通过占位符和URIComponentsBuilder来动态构造URL。*/


        /*第二次修改，使用openfeign，优化写法
        List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
        if(CollUtils.isEmpty(instances)){
            return;
        }
        ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
        ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(instance.getUri() +"/items?ids={ids}",
                HttpMethod.GET, null, new ParameterizedTypeReference<List<ItemDTO>>() {
                }, Map.of("ids",
                        CollUtils.join(itemIds, "，")));
        //collection集合拼接成字符串
        if (!response.getStatusCode().is2xxSuccessful()){
            return;
        }
        List<ItemDTO> items = response.getBody();*/
        //注意此处传入set，和之前预设不一样，改成Collection

        List<ItemDTO> items = itemClient.queryItemByIds(itemIds);

        if (CollUtils.isEmpty(items)) {
            return;
        }
        // 3.转为 id 到 item的map
        Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
        // 4.写入vo
        for (CartVO v : vos) {
            ItemDTO item = itemMap.get(v.getItemId());
            if (item == null) {
                continue;
            }
            v.setNewPrice(item.getPrice());
            v.setStatus(item.getStatus());
            v.setStock(item.getStock());
        }
    }

    @Transactional
    @Override
    public void removeByItemIds(Collection<Long> itemIds) {
        // 1.构建删除条件，userId和itemId
        QueryWrapper<Cart> queryWrapper = new QueryWrapper<Cart>();
        queryWrapper.lambda()
                .eq(Cart::getUserId, UserContext.getUser())
                .in(Cart::getItemId, itemIds);
        // 2.删除
        remove(queryWrapper);
    }

    private void checkCartsFull(Long userId) {
        int count = lambdaQuery().eq(Cart::getUserId, userId).count();
        if (count >= cartProperties.getMaxAmount()) {
            throw new BizIllegalException(StrUtil.format("用户购物车课程不能超过{}", cartProperties.getMaxAmount()));
        }
    }

    private boolean checkItemExists(Long itemId, Long userId) {
        int count = lambdaQuery()
                .eq(Cart::getUserId, userId)
                .eq(Cart::getItemId, itemId)
                .count();
        return count > 0;
    }
}
